package com.basics.framework.utils;

import com.basics.api.vo.UserInfo;
import com.basics.common.utils.SnowFlake;
import com.basics.framework.constants.SecurityConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.swagger.models.auth.In;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author WangWei
 */
@Component
public class JwtYzUtils {
    private static final String CLAIM_KEY_USER_ID = "user_id";
    private static final String CLAIM_KE_SOURCE = "source";
    private static final String CLAIM_KE_OPEN_ID = "open_id";

    private static final String CLAIM_KEY_AUTHORITIES = "roles";

    private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);

    private static String secret;
    private static Long access_token_expiration;
    private static Long refresh_token_expiration;

    @Value("${jwt.base64-secret}")
    public void setSecret(String secret) {
        JwtYzUtils.secret = secret;
    }

    @Value("${jwt.access_token_expiration}")
    public void setAccess_token_expiration(Long access_token_expiration) {
        JwtYzUtils.access_token_expiration = access_token_expiration;
    }

    @Value("${jwt.refresh_token_expiration}")
    public void setRefresh_token_expiration(Long refresh_token_expiration) {
        refresh_token_expiration = refresh_token_expiration;
    }

    private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    /**
     * 获取用户所有角色
     */
    public static List<SimpleGrantedAuthority> getUserRolesByToken(String token) {
        String role = getClaimsFromToken(token).get(CLAIM_KEY_AUTHORITIES).toString();
        return Arrays.stream(role.split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    public static String getUserIdFromToken(String token) {
        String userId;
        try {
            final Claims claims = getClaimsFromToken(token);
            userId = String.valueOf(claims.get(CLAIM_KEY_USER_ID));
        } catch (Exception e) {
            userId = null;
        }
        return userId;
    }

    public static UserInfo getUserFromToken(String token) {
        UserInfo userInfo = new UserInfo();
        try {
            final Claims claims = getClaimsFromToken(token);
            String userId = claims.getSubject();
            if (!StringUtils.isEmpty(userId)) {
                userInfo.setUserId(Long.parseLong(userId));
            }

        } catch (Exception e) {
            userInfo = null;
        }
        return userInfo;
    }

    public static Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    private static Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            refreshedToken = generateAccessToken(claims.getSubject(), claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 校验token
     *
     * @param token
     * @return
     */
    public static Boolean verify(String token, String userId) {
        Claims claims = getClaimsFromToken(token);
        String subject = claims.getSubject();
        return !subject.equalsIgnoreCase(userId) || isTokenExpired(token);
    }

    private static Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private static Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    public static Boolean isTokenExpired(String token) {
        try{
            final Date expiration = getExpirationDateFromToken(token);
            return expiration.before(new Date());
        }catch (Exception e){
            return true;
        }
    }

    private static Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    public static String generateAccessToken(UserInfo userInfo) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USER_ID, userInfo.getUserId());
        return generateAccessToken(String.valueOf(userInfo.getUserId()), claims);
    }

    private static String generateAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, access_token_expiration);
    }

    private static List authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
        List<String> list = new ArrayList<>();
        for (GrantedAuthority ga : authorities) {
            list.add(ga.getAuthority());
        }
        return list;
    }

    private String generateRefreshToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, refresh_token_expiration);
    }

    private static String generateToken(String subject, Map<String, Object> claims, long expiration) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setId(String.valueOf(SnowFlake.nextId()))
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(expiration))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

    public static UserInfo getUserInfo(HttpServletRequest request) {
        String prefix_token = request.getHeader(SecurityConstants.TOKEN_HEADER.toLowerCase());
        if (prefix_token != null && prefix_token.startsWith(SecurityConstants.TOKEN_PREFIX)) {
            String token = prefix_token.replace(SecurityConstants.TOKEN_PREFIX, "");
            return getUserFromToken(token);
        }
        return null;
    }
}
